package com.louis.ylog.tiger;

import com.alibaba.fastjson.JSON;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Message;
import com.dianping.cat.message.Transaction;
import com.dianping.cat.util.StringUtils;
import com.louis.ylog.ELKTagBuilder;
import com.louis.ylog.TraceContext;
import com.louis.ylog.feign.CatConstantsExt;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.spel.SpelCompilerMode;
import org.springframework.expression.spel.SpelParserConfiguration;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
public class TigerAdvice  implements MethodInterceptor {

    private String getMethodString(MethodInvocation invocation){
        try {
            return String.format("%s.%s(%s)",invocation.getMethod().getDeclaringClass().getName(),invocation.getMethod().getName(), Arrays.stream(invocation.getMethod().getGenericParameterTypes()).map(s->".").collect(Collectors.joining("")));
        }catch (Exception ex){
            log.error("获取方法签名异常",ex);
            return "N/A";
        }
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Transaction t = null;
        String methodStr=getMethodString(invocation);
        ELKTagBuilder tagBuilder=getTigerTagBuilder(invocation,methodStr);
        try {
            if(TraceContext.isCatEnable()){
                t = Cat.newTransaction(CatConstantsExt.Type_Tiger_Action,methodStr);
            }
            logRequestToTiger(invocation,tagBuilder);

            //调用 proceed() 方法才会真正的执行实际被代理的方法
            Object result = invocation.proceed();

            logResponseToTiger(invocation,result,tagBuilder);
            if(null!=t){
                t.setStatus(Message.SUCCESS);
            }
            return result;
        } catch (Throwable te) {
            logResponseExceptionToTiger(invocation,te,tagBuilder);
            if(null!=t){
                t.setStatus(te);
            }
            throw te;
        }finally {
            if(null!=t){
                t.complete();
            }
        }
    }

    /**
     * 缓存的方法调用信息
     */
    public static final ConcurrentHashMap<Class, List<Field>> CachedTigerMethodFields=new ConcurrentHashMap<>();


    private ELKTagBuilder getTigerTagBuilder(MethodInvocation invocation,String methodStr){
        ELKTagBuilder builder=ELKTagBuilder.create().saveInTiger();
        try {
            TigerLog tigerLog = invocation.getMethod().getAnnotation(TigerLog.class);
            if(tigerLog==null){
                return builder;
            }

            //日志标题
            if(!StringUtils.isEmpty(tigerLog.title())){
                builder.title(tigerLog.title());
            }else{

                //接口形式的
                ApiOperation apiOperation=invocation.getMethod().getAnnotation(ApiOperation.class);
                if(apiOperation!=null&&!StringUtils.isEmpty(apiOperation.value())){
                    builder.title(apiOperation.value());
                }else{
                    builder.title(methodStr);
                }
            }
            builder.putCustomerField("TigerMethod",methodStr);

            //获取方法参数类内部的key
            if(invocation.getArguments()!=null&&invocation.getArguments().length>0){
                for (Object obj:invocation.getArguments()){
                    if(obj==null){
                        continue;
                    }
//                    if(obj instanceof Closeable){
//                        continue;
//                    }
                    if(!CachedTigerMethodFields.containsKey(obj.getClass())){
                        continue;
                    }
                    for (Field tigerField:CachedTigerMethodFields.get(obj.getClass())){
                        TigerKey fieldTigerKey = tigerField.getAnnotation(TigerKey.class);
                        if(fieldTigerKey==null){
                            continue;
                        }
                        String fieldValue=String.valueOf(tigerField.get(obj));
                        if(!StringUtils.isEmpty(fieldTigerKey.keyName())){
                            builder.putCustomerField(fieldTigerKey.keyName(),fieldValue);
                        }else{
                            builder.putCustomerField(tigerField.getName(),fieldValue);
                        }
                    }
                }
            }

            //获取方法参数前的key
            Annotation[][] paramAnnotations =  invocation.getMethod().getParameterAnnotations();
            if(paramAnnotations!=null&&paramAnnotations.length>0){
                for (int paramIndex=0;paramIndex<paramAnnotations.length;paramIndex++){
                    Annotation[] annotations=paramAnnotations[paramIndex];
                    if(annotations!=null&&annotations.length>0){
                        for (Annotation annotation:annotations){
                            if(!annotation.annotationType().equals(TigerKey.class)){
                                continue;
                            }
                            builder.putCustomerField(((TigerKey)annotation).keyName(),String.valueOf(invocation.getArguments()[paramIndex]));
                        }
                    }
                }
            }

        }catch (Exception ex){
            log.error(builder.build(),"保存工程虎日志异常",ex);
        }
        return builder;
    }

    private void logRequestToTiger(MethodInvocation invocation, ELKTagBuilder builder){
        try {
            log.info(builder.build(), "tiger请求: {}",  JSON.toJSONString(invocation.getArguments()));
        }catch (Exception ex){
            log.error(builder.build(),"保存工程虎日志异常",ex);
        }
    }

    private void logResponseToTiger(MethodInvocation invocation,Object result,ELKTagBuilder builder){
        try {
            //检查是否有新key
            if(result!=null&&CachedTigerMethodFields.containsKey(result.getClass())){
                for (Field tigerField:CachedTigerMethodFields.get(result.getClass())){
                    TigerKey fieldTigerKey = tigerField.getAnnotation(TigerKey.class);
                    if(fieldTigerKey==null){
                        continue;
                    }
                    String fieldValue=String.valueOf(tigerField.get(result));
                    if(!StringUtils.isEmpty(fieldTigerKey.keyName())){
                        builder.putCustomerField(fieldTigerKey.keyName(),fieldValue);
                    }else{
                        builder.putCustomerField(tigerField.getName(),fieldValue);
                    }
                }
            }

            //检查是否需要报警
            boolean needAlert=false;
            TigerLog tigerLog = invocation.getMethod().getAnnotation(TigerLog.class);
            if(result!=null&&tigerLog!=null&&!StringUtils.isEmpty(tigerLog.alertSpEL())){
                try {
                    //Spel 解析配置器
                    SpelParserConfiguration configuration=new SpelParserConfiguration(SpelCompilerMode.IMMEDIATE,result.getClass().getClassLoader());
                    //解析器
                    SpelExpressionParser parser=new SpelExpressionParser(configuration);
                    //上下文
                    EvaluationContext context=new StandardEvaluationContext(result);
                    needAlert = parser.parseExpression(tigerLog.alertSpEL()).getValue(context,Boolean.class);
                }catch (Exception ex){
                    log.error(builder.build(),"执行SpEL异常",ex);
                }
            }
            //记日志
            if(needAlert){
                log.error(builder.build(),"tiger报警响应: {}",JSON.toJSONString(result));
            }else{
                log.info(builder.build(),"tiger响应: {}",JSON.toJSONString(result));
            }
        }catch (Exception ex){
            log.error(builder.build(),"保存工程虎日志异常",ex);
        }
    }

    private void logResponseExceptionToTiger(MethodInvocation invocation,Throwable te,ELKTagBuilder builder){
        try {
            log.error(builder.build(),"tiger异常",te);
        }catch (Exception ex){
            log.error(builder.build(),"保存工程虎日志异常",ex);
        }
    }

}
